package com.krickert.ipsearch;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.NumericField;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.spatial.tier.projections.CartesianTierPlotter;
import org.apache.lucene.spatial.tier.projections.IProjector;
import org.apache.lucene.spatial.tier.projections.SinusoidalProjector;
import org.apache.lucene.util.NumericUtils;
import com.krickert.ipsearch.city.IpSearchCityBean;
import com.krickert.lucene.IndexWriterManager;
/**
* Continually takes data from a queue and puts it into an index. The indexer
* itself is already multi threaded so this only runs on a single thread.
*
* @author krickert
*
*/
public class IndexIpAddressTask {
private static final Log log = LogFactory.getLog(IndexIpAddressTask.class);
private final IndexWriter writer;
private final BlockingQueue<IpSearchCityBean> queue;
private final int timeout;
private static final String latField = "lat";
private static final String lngField = "lon";
private static final String tierPrefix = "_localTier";
/**
* The task of this indexer is to create the spatial ip address index. There
* is an executor that creates a configurable number of threads and will all
* hit the same index writer index.
*
* The data will come from the concurrent queue which will be concurrently
* read by the reader thread as this is being indexed. Once the reader thread
* is complete, it will set the lastEntryInQueue boolean to notify the other
* threads that it's time to exit and that no more messages will be coming
* into the queue.
*
* @param writer
* a wrapper that created the writer object to create the index
* @param queue
* the concurrent queue that the reader is sending objects into for
* lucene to write
* @throws IOException
* if something goes wrong with the indexer, the thread exits,
* violently
*/
public IndexIpAddressTask(IndexWriterManager writer, BlockingQueue<IpSearchCityBean> queue, int timeout) {
this.writer = checkNotNull(writer.getWriter());
this.queue = checkNotNull(queue);
this.timeout = timeout;
}
public void insertIntoIndex() {
log.info("Starting to insert into the indexer");
try {
IpSearchCityBean bean;
boolean done = false;
while (!done) {
bean = queue.poll(timeout, TimeUnit.SECONDS);
if (bean != null) {
addLocation(bean);
} else {
log.info("Marking as complete.");
done = true;
}
}
} catch (IOException e) {
throw new IllegalStateException(e);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}// while loop continues
/**
* Adds IPSearch data to the index.
*
* @param bean
* the bean it needs to index
* @throws IOException
*/
public void addLocation(IpSearchCityBean bean) throws IOException {
Document doc = new Document();
doc.add(new NumericField("ip_start", Field.Store.YES, true).setLongValue(bean.getIpStart()));
doc.add(new NumericField("ip_end", Field.Store.YES, true).setLongValue(bean.getIpEnd()));
doc.add(new NumericField("ip_start_a", Field.Store.NO, true).setLongValue((bean.getIpStart() / 16777216l) % 256));
doc.add(new NumericField("ip_start_b", Field.Store.NO, true).setLongValue((bean.getIpStart() / 65536) % 256));
doc.add(new NumericField("ip_start_c", Field.Store.NO, true).setLongValue((bean.getIpStart() / 256) % 256));
doc.add(new NumericField("ip_start_d", Field.Store.NO, true).setLongValue((bean.getIpStart()) % 256));
doc.add(new NumericField("ip_end_a", Field.Store.NO, true).setLongValue((bean.getIpEnd() / 16777216l) % 256));
doc.add(new NumericField("ip_end_b", Field.Store.NO, true).setLongValue((bean.getIpEnd() / 65536) % 256));
doc.add(new NumericField("ip_end_c", Field.Store.NO, true).setLongValue((bean.getIpEnd() / 256) % 256));
doc.add(new NumericField("ip_end_d", Field.Store.NO, true).setLongValue((bean.getIpEnd()) % 256));
doc.add(new Field(latField, NumericUtils.doubleToPrefixCoded(bean.getLat()), Field.Store.YES, Field.Index.NOT_ANALYZED));
doc.add(new Field(lngField, NumericUtils.doubleToPrefixCoded(bean.getLon()), Field.Store.YES, Field.Index.NOT_ANALYZED));
// some of these fields have a chance of being null
addToDoc(doc, "city", bean.getCity(), Field.Store.YES, Field.Index.ANALYZED);
addToDoc(doc, "zip_code", bean.getZipCode(), Field.Store.YES, Field.Index.ANALYZED);
addToDoc(doc, "country_code", bean.getCountryCode(), Field.Store.YES, Field.Index.ANALYZED);
addToDoc(doc, "country_name", bean.getCountryName(), Field.Store.YES, Field.Index.ANALYZED);
addToDoc(doc, "metro_code", bean.getMetroCode(), Field.Store.YES, Field.Index.ANALYZED);
addToDoc(doc, "region_code", bean.getRegionCode(), Field.Store.YES, Field.Index.ANALYZED);
addToDoc(doc, "region_name", bean.getRegionName(), Field.Store.YES, Field.Index.ANALYZED);
IProjector projector = new SinusoidalProjector();
int startTier = 5;
int endTier = 15;
for (; startTier <= endTier; startTier++) {
CartesianTierPlotter ctp;
ctp = new CartesianTierPlotter(startTier, projector, tierPrefix);
double boxId = ctp.getTierBoxId(bean.getLat(), bean.getLon());
doc.add(new Field(ctp.getTierFieldName(), NumericUtils.doubleToPrefixCoded(boxId), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
}
writer.addDocument(doc);
}
private static void addToDoc(Document doc, String field, String value, Field.Store store, Index analyzed) {
if (!emptyString(value)) {
doc.add(new Field(field, value, store, analyzed));
}
}
private static boolean emptyString(String string) {
if (string != null && !string.trim().equals("")) {
return false;
} else {
return true;
}
}
public void commit() {
log.info("committing..");
try {
writer.commit();
} catch (CorruptIndexException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void commitAndFinish() {
log.info("committing..");
try {
writer.commit();
log.info("closing..");
writer.close();
log.info("Write complete");
} catch (CorruptIndexException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}// end private class